package com.norma.spring.framework.context;

import com.norma.spring.core.annotation.NormaAutowired;
import com.norma.spring.core.annotation.NormaController;
import com.norma.spring.core.annotation.NormaService;
import com.norma.spring.framework.beans.NormaBeanFactory;
import com.norma.spring.framework.beans.NormaBeanWrapper;
import com.norma.spring.framework.beans.config.NormaBeanDefinition;
import com.norma.spring.framework.beans.config.NormaBeanPostProcessor;
import com.norma.spring.framework.beans.support.NormaBeanDefinitionReader;
import com.norma.spring.framework.beans.support.NormaDefaultListableBeanFactory;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author zhangchaofeng
 * @date 2020/11/16
 * @description
 */
public class NormaApplicationContext extends NormaDefaultListableBeanFactory
        implements NormaBeanFactory {

    private String[] configLocations;

    private NormaBeanDefinitionReader beanDefinitionsReader;

    /**
     * 单例Bean容器
     */
    private Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    /**
     * 通用IOC容器
     */
    private Map<String, NormaBeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>(256);


    public NormaApplicationContext(String... configLocations) {
        this.configLocations = configLocations;
        refresh();
    }

    @Override
    protected void refresh() {
        //定位配置文件
        beanDefinitionsReader = new NormaBeanDefinitionReader();
        //加载配置文件 扫描相关的类，封装成BeanDefinition
        List<NormaBeanDefinition> beanDefinitions =
                this.beanDefinitionsReader.loadBeanDefinitions();
        //注册 将bean注册到 伪IOC容器
        doRegisterBeanDefinition(beanDefinitions);
        //初始化非延时加载的类
        try {
            doAutowired();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 处理非延时加载的类
     */
    private void doAutowired() throws Exception {
        for (Map.Entry<String, NormaBeanDefinition> stringNormaBeanDefinitionEntry : super.beanDefinitionMap.entrySet()) {
            if (!stringNormaBeanDefinitionEntry.getValue().isLazyInit()) {
                getBean(stringNormaBeanDefinitionEntry.getKey());
            }
        }

    }

    /**
     * 将配置文件注册到容器
     */
    private void doRegisterBeanDefinition(List<NormaBeanDefinition> beanDefinitions) {
        for (NormaBeanDefinition beanDefinition : beanDefinitions) {
            super.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
        }

    }

    @Override
    public Object getBean(String beanName) throws Exception {
        NormaBeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        //先注入后初始化，解决循环依赖的问题
        //初始化
        Object instance = instantiateBean(beanDefinition);
        NormaBeanPostProcessor normaBeanPostProcessor = new NormaBeanPostProcessor();
        //Bean初始化前置处理
        Object beforeInitialization = normaBeanPostProcessor.postProcessBeforeInitialization(instance, beanName);
        //将BeanWrapper放入IOC容器中
//        if (factoryBeanInstanceCache.containsKey(beanDefinition.getFactoryBeanName())) {
//            throw new Exception("The" + beanDefinition.getFactoryBeanName() + "has exists!");
//        }
        NormaBeanWrapper normaBeanWrapper = new NormaBeanWrapper(instance,instance.getClass());
        this.factoryBeanInstanceCache.put(beanDefinition.getFactoryBeanName(), normaBeanWrapper);
        //DI注入
        populateBean(beanDefinition, normaBeanWrapper);
        return this.factoryBeanInstanceCache.get(beanDefinition.getFactoryBeanName()).getWrappedInstance();
    }

    /**
     * 根据类型获取Bean
     */
    public Object getBean(Class<?> clazz) throws Exception {
        return this.getBean(clazz.getSimpleName());
    }

    /**
     * 依赖注入
     */
    private void populateBean(NormaBeanDefinition normaBeanDefinition,
                              NormaBeanWrapper normaBeanWrapper) {
        try {
            Class<?> clazz = normaBeanWrapper.getWrappedClass();
            //忽略不加注解的Bean
            if (!(clazz.isAnnotationPresent(NormaController.class) ||
                    clazz.isAnnotationPresent(NormaService.class))) {
                return;
            }
            for (Field declaredField : clazz.getDeclaredFields()) {
                if (declaredField.isAnnotationPresent(NormaAutowired.class)) {
                    NormaAutowired annotation = declaredField.getAnnotation(NormaAutowired.class);
                    String autowiredBeanName = annotation.value().trim();
                    if ("".equals(autowiredBeanName)) {
                        //没有指定注入的beamName，默认使用类型名,并且小写
                        autowiredBeanName = NormaBeanDefinitionReader
                                .toLowerFirstCase(declaredField.getType().getSimpleName());
                    }
                    //开启访问
                    declaredField.setAccessible(true);
                    //注入
                    declaredField.set(normaBeanWrapper.getWrappedInstance(),
                            factoryBeanInstanceCache.get(autowiredBeanName).getWrappedInstance());


                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 初始化Bean
     */
    private Object instantiateBean(NormaBeanDefinition beanDefinition) {
        Object instance = null;
        try {
            //拿到需要实例化的类
            String beanClassName = beanDefinition.getBeanClassName();
            Class<?> aClass = Class.forName(beanClassName);
            if (singletonObjects.containsKey(beanClassName)) {
                instance = singletonObjects.get(beanClassName);
            } else {
                //反射实例化
                instance = aClass.newInstance();
                //放入容器
                //根据className 或 beanName 都可以放入容器
                singletonObjects.put(beanClassName, instance);
                singletonObjects.put(beanDefinition.getFactoryBeanName(), instance);
            }
            //将对象封装到BeanWrapper中
        } catch (Exception e) {
            e.printStackTrace();
        }
        return instance;
    }

}
